Android 6.0 运行时权限浅析

关于 Android 的权限是一直都存在的,6.0 之后做的改变其实是为了更好的保护用户的个人信息安全。

最直观的就是以前安装应用的时候会显示这个应用需要用到所有的权限,如果不想应用拥有某个权限,唯一的做法就是放弃该应用。现在不一样了,现在是先不管应用需要哪些权限,安装了再说,等要用到某些功能时应用才提示要申请权限,当然也是可以拒绝的,大不了不用这个功能而已,但是其他的功能还是照样可以用的啊。

接下来就简单的介绍一下关于 6.0 运行时权限的使用以及一些注意事项。

Android 中的权限有 100 多种,主要分为普通权限和危险权限,其中普通权限只需在 Androidmanifest.xml 文件中声明即可;而危险权限除了需要在 Androidmanifest.xml 文件中声明,还需在代码中进行处理。

运行时权限是以组的形式存在的,一共 9 组 24 个权限。如果用户同意应用申请的某个权限,那么应用可以使用该权限所在的那一组权限。比如应用申请 CALL_PHONE 这个权限,如果用户同意了,那么这个应用将拥有 PHONE 这个权限组所有的权限。

使用方法

这里通过一个拨打电话的小例子来展示运行时权限的用法:

首先在布局文件添加一个按钮,这个按钮用来申请权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="cn.ljuns.androidgrowing.MainActivity">

<Button
android:id="@+id/btn_call"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Call Phone" />

</LinearLayout>

很简单的布局,只是添加了一个 Button。既然是拨打电话,那么就需要在 AndroidManifest.xml 文件中进行权限的声明:

1
<uses-permission android:name="android.permission.CALL_PHONE" />

在 AndroidManifest.xml 文件进行权限的声明这一点没有什么变化,以前是这样,现在也是这样。接下来才是重头戏,看看对应的 MainActivity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 绑带 Button 事件
findViewById(R.id.btn_call).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 检查权限
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// 申请拨打电话的权限
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CALL_PHONE}, 1);
} else {
call();
}
}
});

/**
* 拥有权限时拨打电话
*/
private void call() {
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
} catch (SecurityException e) {
e.printStackTrace();
}
}

/**
*申请权限的回调
*/
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
call();
} else {
Toast.makeText(this, "你拒绝了这个权限", Toast.LENGTH_SHORT).show();
}
break;
}
}
}

首先需要通过 ContextCompat.checkSelfPermission() 方法来检查某个权限,如果返回值等于 PackageManager.PERMISSION_GRANTED 就代表已经拥有该权限,而如果返回值等于 PackageManager.PERMISSION_DENIED 则代表还未拥有该权限。

其次,如果还未拥有该权限就通过 ActivityCompat.requestPermissions(final Activity activity, final String[] permissions, final int requestCode) 方法来申请。三个参数分别是:Activity 对象、需要申请的权限数组、请求码。当然,Fragment 中同样可以申请权限: requestPermissions(String[] permissions, int requestCode)

最后需要对申请的权限进行处理,也就是同意或者拒绝了该权限后需要执行哪些操作。所以还需要重写 onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 方法,在该方法中进行处理。onRequestPermissionsResult() 方法的三个参数分布是:申请权限时传递过来的请求码、申请的权限数组、申请权限的返回值(同意或者拒绝)。

下面来运行下这个小例子:

如果第一次拒绝了该权限,在下次要用到的时候会再次提示需要申请权限;而如果已经允许了该权限,在下次需要用到的时候就不会再申请了。

其实还有一点需要注意:如果手机的 Android 版本低于 6.0,运行时权限不会起任何的作用。

简单封装

接下来尝试简单的封装一下:

首先需要一个接口,里面的两个方法分别代表权限申请成功和失败,也就是允许和拒绝。同时,对于拒绝的权限还要显示哪些权限被拒绝了:

1
2
3
4
public interface PermissionListener {
void granted();
void denied(List<String> deniedList);
}

接着新建一个 BaseActivity 继承 AppCompatActivity,在 BaseActivity 中做权限的申请和申请后的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private static PermissionListener mListener;
/**
* 申请权限
*/
public static void requestRuntimePermissions(
String[] permissions, PermissionListener listener) {
mListener = listener;
List<String> permissionList = new ArrayList<>();
// 遍历每一个申请的权限,把没有通过的权限放在集合中
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED) {
permissionList.add(permission);
} else {
mListener.granted();
}
}
// 申请权限
if (!permissionList.isEmpty()) {
ActivityCompat.requestPermissions(this,
permissionList.toArray(new String[permissionList.size()]), 1);
}
}

/**
* 申请后的处理
*/
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

if (grantResults.length > 0) {
List<String> deniedList = new ArrayList<>();
// 遍历所有申请的权限,把被拒绝的权限放入集合
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i];
if (grantResult == PackageManager.PERMISSION_GRANTED) {
mListener.granted();
} else {
deniedList.add(permissions[i]);
}
}
if (!deniedList.isEmpty()) {
mListener.denied(deniedList);
}
}
}

上面代码比较简单,这里再介绍一下。

  1. 在 requestRuntimePermissions() 方法中首先遍历应用需要的权限,如果权限还没有申请或者之前申请失败的就先放入一个集合;如果已经申请成功的就回调 granted() 方法,让用户去处理后续。接着对那些还没申请过或者之前申请失败的权限进行申请。
  2. 重写 onRequestPermissionsResult() 方法,遍历申请的所有权限,如果申请通过了就回调 granted() 方法,让用户去做后续处理;如果申请失败就放入集合,并且回调 denied() 方法,让用户去做后续处理。

MainActivity 中的代码需要修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 绑带 Button 事件
findViewById(R.id.btn_demo).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 申请权限
requestRuntimePermissions(new String[]{Manifest.permission.CALL_PHONE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_FINE_LOCATION},
new PermissionListener() {
@Override
public void granted() {
// 权限申请通过
}

@Override
public void denied(List<String> deniedList) {
// 权限申请不通过
for (String denied : deniedList) {
Toast.makeText(MainActivity.this,
denied + "权限被拒绝了",
Toast.LENGTH_SHORT).show();
}
}
});
}
});
}

最后运行结果:

参考链接:Android 6.0 运行时权限讲解

坚持原创技术分享,您的支持将鼓励我继续创作!